#include "ffz/raft/raft-role.h"
#include "ffz/raft/raft-packet.h"
#include "ffz/raft/raft-log.h"
#include "ffz/raft/raft-table.h"
#include "ffz/ffzconfig.h"
#define DEBUG DEBUG_PRINT
#include "ffz/debug.h"
// constant value
uint16_t role = ROLE_FOLLOWER;
uint16_t currentTerm = 0;
uint16_t votedFor = 0;
uint16_t currentLeaderId = 0;
// candidate value
uint8_t getVoteNum = 0;
// leader value
uint16_t nextIndexs[NODE_NUM + 1];
uint16_t matchIndexs[NODE_NUM + 1];
uint8_t voteGranteds[NODE_NUM + 1];

void initRoleVariables()
{
    uint8_t i = 0;
    // first log will not be used
    RaftLog *log = getRaftLog(0);
    log->index = 0;
    log->term = 0;
    for (; i < NODE_NUM + 1; ++i)
    {
        nextIndexs[i] = 1;
        matchIndexs[i] = 0;
    }
}

//ROLE_FOLLOWER APIs
void TIMER_BecomeCandiate()
{
    if (role == ROLE_FOLLOWER)
    {
        PRINTF("I'm decide to become the candidate \n");
    }
    else if (role == ROLE_CANDIDATE)
    {
        PRINTF("I'm come back to become the candidate \n");
    }
    else
    {
        return;
    }
    //1.1 increase the term counter
    currentTerm++;
    //1.2 vote for himself
    role = ROLE_CANDIDATE;
    votedFor = raftTableLocalId();
    getVoteNum = 1;
    // the node has no leader now
    currentLeaderId = 0;
    if (getVoteNum > getRaftNodeTableLength() / 2)
    {
        // if there's only one node in this net, it will not send RPC to others.
        ROLE_ToLeader();
        return;
    }
}

uint8_t RPC_ValidateChecking(uint16_t term, uint16_t candidateId, uint16_t lastLogIndex, uint16_t lastLogTerm)
{
    if (term <= currentTerm)
    {
        // the candidate's currentTerm must be greater than self's currentTerm.
        return FALSE;
    }
    ROLE_ToFollower(term);
    // to check the newest log
    uint16_t myLastLogIndex, myLastLogTerm;
    getLastLogIndexAndTerm(&myLastLogIndex, &myLastLogTerm);
    if (lastLogTerm < myLastLogTerm)
    {
        //the candidate's lastLogTerm must be greater than or equal to self lastLogTerm.
        return FALSE;
    }
    if (lastLogIndex < myLastLogIndex)
    {
        //the candidate's lastLogIndex must be greater than or equal to self lastLogIndex.
        return FALSE;
    }
    return TRUE;
}

void RPC_VoteFor(uint16_t term, uint16_t candidateId)
{
    role = ROLE_FOLLOWER;
    currentTerm = term;
    votedFor = candidateId;
}

//ROLE_CANDIDATE APIs
void TIMER_PeriodicCandidateRequest()
{
    if (role != ROLE_CANDIDATE)
    {
        return;
    }
    //1.3 reset the timer, this work will not finish here.
    //1.4 broadcast this RPC
    uint16_t lastLogIndex, lastLogTerm;
    getLastLogIndexAndTerm(&lastLogIndex, &lastLogTerm);
    createRaftPacketCandidate(currentTerm, raftTableLocalId(), lastLogIndex, lastLogTerm, getSendBuffer());
    raftPacketSendBroadcast(getSendBuffer(), 9);
}
uint8_t RPC_GetVote()
{
    if (role != ROLE_CANDIDATE)
    {
        //only the ROLE_CANDIDATE can process this type of action
        return 0;
    }
    getVoteNum++;
    if (getVoteNum > getRaftNodeTableLength() / 2)
    {
        ROLE_ToLeader();
    }
    return 1;
}
void ROLE_ToLeader()
{
    uint8_t i;
    uint16_t lastLogIndex, lastLogTerm;
    getLastLogIndexAndTerm(&lastLogIndex, &lastLogTerm);
    if (role != ROLE_CANDIDATE)
    {
        return;
    }
    role = ROLE_LEADER;
    currentLeaderId = raftTableLocalId();
    for (i = 0; i < NODE_NUM; ++i)
    {
        nextIndexs[i] = lastLogIndex + 1;
        matchIndexs[i] = 0;
    }
    PRINTF("I Became the Leader\n");
}

//ROLE CANDIDATE & FOLLOWER APIs
uint8_t receiveHeartBeats(uint16_t term, uint16_t leaderId, uint16_t prevIndex, uint16_t prevTerm, uint16_t commitIndex, uint8_t hasEntry, RaftLog *entry)
{
    // check whether the LEADER , CANDIDATE or FOLLOWER needs to become a FOLLOWER with a new term
    if (currentTerm < term)
    {
        // a leader received a higher term "HeartBeats" RPC
        // it will be back to a follower
        ROLE_ToFollower(term);
        currentLeaderId = leaderId;
    }
    else if (currentTerm > term)
    {
        // leader's term is smaller than the follower
        return FALSE;
    }
    // a leader received a lower or same term "HeartBeats" RPC
    // this will do nothing, just wait for "HeartBeats" sending by itself
    uint16_t lastLogIndex, lastLogTerm;
    getLastLogIndexAndTerm(&lastLogIndex, &lastLogTerm);
    if (prevIndex > lastLogIndex)
    {
        return FALSE;
    }
    if (prevTerm > lastLogTerm)
    {
        return FALSE;
    }
    if (hasEntry)
    {
        // a new log will be added
        lastLogIndex = entry->index;
        lastLogTerm = entry->term;
        setRaftLog(entry);
        setRaftLogLastIndex(entry->index);
    }
    // could add commit index successfully ?
    setCommitIndex(commitIndex);
    return TRUE;
}

//ROLE_LEADER APIs
void TIMER_LeaderBeats()
{
    uint8_t i;
    // if the role is not the LEADER, it won't do this task
    if (role != ROLE_LEADER)
    {
        return;
    }
    uint16_t lastLogIndex, lastLogTerm;
    getLastLogIndexAndTerm(&lastLogIndex, &lastLogTerm);
    for (i = 0; i < NODE_NUM; ++i)
    {
        if (i + 1 == raftTableLocalId())
        {
            // will not send to itself
            continue;
        }
        uint16_t prevIndex = nextIndexs[i] - 1;
        uint16_t prevTerm = getRaftLog(prevIndex)->term;
        uint8_t hasEntry = nextIndexs[i] <= lastLogIndex;
        RaftLog *entry = NULL;
        if (hasEntry)
        {
            // send entries
            entry = getRaftLog(nextIndexs[i]);
        }
        uint16_t commitIndex = getCommitIndex();
        if (nextIndexs[i] < commitIndex)
        {
            commitIndex = nextIndexs[i];
        }
        createRaftPacketHeartbeats(currentTerm, currentLeaderId, prevIndex, prevTerm, commitIndex, hasEntry, entry, getSendBuffer());
        // PRINTF("RPC_HEARTBEATS term=%u leaderId=%u prevIndex=%u prevTerm=%u commitIndex=%u hasEntry=%u  index=%u term=%u status=%u action=%u \n", currentTerm, currentLeaderId, prevIndex, prevTerm, commitIndex, hasEntry, entry->index, entry->term, entry->status, entry->action);
        // extractRaftPacketHeartbeats(&currentTerm, &currentLeaderId, &prevIndex, &prevTerm, &commitIndex, &hasEntry, entry, getSendBuffer());
        raftPacketSendUnicast(getSendBuffer(), 20, &getRaftNodeTableItem(i)->ip);
        // raftPacketSendBroadcast(getSendBuffer(), 18);
    }
    PRINTF("beats...\n");
}
void receiveHeartBeatsResp(uint16_t term, uint8_t success, uint16_t matchIndex, uint16_t senderId)
{
    if (term > currentTerm)
    {
        ROLE_ToFollower(term);
        return;
    }
    if (success)
    {
        // follower's log status error
        nextIndexs[senderId - 1] = matchIndex + 1;
        matchIndexs[senderId - 1] = matchIndex;
    }
    else
    {
        // follower's log status matched
        nextIndexs[senderId - 1]--;
    }
}
void OPERATION_AddLog()
{
    if (role != ROLE_LEADER)
    {
        printf("can't add log, not leader\n");
        return;
    }
    addRaftLog(currentTerm, 1, 1);
}

//ROLE CANDIDATE & LEADER APIs
void ROLE_ToFollower(uint16_t term)
{
    role = ROLE_FOLLOWER;
    currentTerm = term;
    votedFor = 0;
}
void OPERATION_ShowLogs()
{
    uint16_t i;
    uint16_t lastLogIndex = getLastLogIndex();
    printf("term:%u lastIndex:%u ", currentTerm, lastLogIndex);
    for (i = 1; i <= lastLogIndex; ++i)
    {
        RaftLog *log = getRaftLog(i);
        printf("%u:%u ", log->index, log->term);
    }
    printf("\n");
}
//some tool inner function

//getter and setter
uint16_t getCurrentTerm()
{
    return currentTerm;
}
uint16_t getVotedFor()
{
    return votedFor;
}
void setCurrentTerm(uint16_t val)
{
    currentTerm = val;
}
void setVotedFor(uint16_t val)
{
    votedFor = val;
}